Код:
	//=============================================================================
// Phileas_Cursor.js
//=============================================================================
// [Update History]
// 2023.August.20 Ver1.0.0 First Release
// 2023.August.21 Ver1.1.0 Added show/hide parameters and commands
// 2023.August.24 Ver1.1.1 Fixed gamepad
// 2024.March.11 Ver1.2.0 Added event cursor settings
// 2024.March.13 Ver1.2.1 Event cursor data saves and global command
// 2024.March.16 Ver1.3.0 Offset, battle cursor, menu cursor, click picture, animation
// 2024.March.18 Ver1.3.1 Preloading, optimization
// 2024.March.19 Ver1.3.2 Optimization
/*:
 * @target MZ
 * @plugindesc Advanced cursor configuration
 * @author Phileas
 *
 * @param basicCursors
 * @text Basic cursors
 *
 * @param defaultCursor
 * @parent basicCursors
 * @text Default cursor
 * @type struct<CursorDataStruct>
 * @desc It is set at the start of the game
 *
 * @param battleCursor
 * @parent basicCursors
 * @text Battle cursor
 * @type struct<CursorDataStruct>
 * @desc Used in battle. If not set, the "Default cursor" will be used
 *
 * @param menuCursor
 * @parent basicCursors
 * @text Menu cursor
 * @type struct<CursorDataStruct>
 * @desc Used in menu. If not set, the "Default cursor" will be used
 *
 * @param cursorDisplay
 * @text Cursor display
 *
 * @param hideAtStartup
 * @parent cursorDisplay
 * @text Hide at startup?
 * @type boolean
 * @default false
 * @desc If true, the cursor will be invisible when the game starts
 *
 * @param keyboardHideKey
 * @parent cursorDisplay
 * @text Keyboard key to hide
 * @desc The invisibility of the cursor will switch when the specified key is pressed
 *
 * @param keyboardHideKeyNumber
 * @parent cursorDisplay
 * @text Keyboard key to hide (number)
 * @type number
 * @default 0
 * @desc The alternative to the "Keyboard key to hide" parameter, if it is more convenient for you to use a numeric key code
 *
 * @param mouseHideKey
 * @parent cursorDisplay
 * @text Mouse key to hide
 * @desc The invisibility of the cursor will switch when the specified key is pressed
 *
 * @param mouseHideKeyNumber
 * @parent cursorDisplay
 * @text Mouse key to hide (number)
 * @type number
 * @default 0
 * @desc The alternative to the "Mouse key to hide" parameter, if it is more convenient for you to use a numeric key code
 *
 * @param gamepadHideKey
 * @parent cursorDisplay
 * @text Gamepad key to hide
 * @desc The invisibility of the cursor will switch when the specified key is pressed
 *
 * @param gamepadHideKeyNumber
 * @parent cursorDisplay
 * @text Gamepad key to hide (number)
 * @type number
 * @default 0
 * @desc The alternative to the "Gamepad key to hide" parameter, if it is more convenient for you to use a numeric key code
 *
 * @param animationPeriod
 * @text Animation period
 * @type number
 * @default 100
 * @min 0
 * @desc The number of milliseconds after which the cursor animation frames should be updated.
 *
 * @param mapsPreload
 * @text Maps preloading
 * @type boolean
 * @default false
 * @desc Preloading of cursor images from commands on maps. Loaded internal folders of any nesting level.
 *
 * @param commonEventsPreload
 * @text Common events preloading
 * @type boolean
 * @default false
 * @desc Preloading of cursor images from commands in common events. Loaded internal folders of any nesting level.
 *
 * @command setDefaultCursor
 * @text Change the default cursor
 * @arg cursorData
 * @text Configuration
 * @type struct<CursorDataStruct>
 *
 * @command setBattleCursor
 * @text Change the battle cursor
 * @arg cursorData
 * @text Configuration
 * @type struct<CursorDataStruct>
 *
 * @command setMenuCursor
 * @text Change the menu cursor
 * @arg cursorData
 * @text Configuration
 * @type struct<CursorDataStruct>
 *
 * @command hide
 * @text Hide cursor
 * @desc Makes the cursor invisible
 *
 * @command show
 * @text Show cursor
 * @desc Makes the cursor visible
 *
 * @command setEventCursorData
 * @text Configure an event
 * @desc Setting the cursor configuration for a single event
 * @arg eventId
 * @text Event ID
 * @type number
 * @desc Input a event ID (it is a positive number)
 * @arg cursorEventData
 * @text Configuration
 * @type struct<CursorEventDataStruct>
 *
 * @command setGlobalEventCursorData
 * @text Configure an event (global)
 * @desc Setting the cursor configuration for a single event on any map
 * @arg mapId
 * @text Map ID
 * @type number
 * @desc Input a map ID (it is a positive number)
 * @arg eventId
 * @text Event ID
 * @type number
 * @desc Input a event ID (it is a positive number)
 * @arg cursorEventData
 * @text Configuration
 * @type struct<CursorEventDataStruct>
 *
 * 
 * @help
 * Changes the cursor image to any of the img/system.
 * Animated cursors are supported, details below.
 * Use png format!
 *
 * To change the cursor to the standard one, do not select an image in the parameter/argument (option "(None)").
 * 
 * Plugin commands: 
 * - "Change the default cursor"
 * - "Change the battle cursor"
 * - "Change the menu cursor"
 * - "Hide cursor"
 * - "Show cursor"
 * - "Configure an event" - configures the cursor for a single event
 * - "Configure an event (global)" - configures the cursor for a single event on any map
 *
 * You can set the cursor configuration for an event using the plugin command and using tags in the event notes:
 * <CursorPicture:picture> - this picture (picture.png) will be used when the cursor is hovered over the event
 * <CursorPictureOnClick:pictureClick> - the picture displayed when clicked (if not specified, the cursor will not change when clicked)
 * <CursorXOffset:5> - the click point will be shifted 5 pixels horizontally from the upper-left corner
 * <CursorYOffset:5> - the click point will be shifted 5 pixels vertically from the upper-left corner
 * <CursorFramesNumber:3> - the cursor will be animated with 3 frames of animation
 * <CursorClickFramesNumber:3> - the cursor will be animated with 3 frames of animation when clicked
 * <CursorStartOnClick> - the event will be triggered when you click on it
 * <CursorStartOnHover> - the event will be triggered when the cursor is hovered over it
 *
 * The settings set by the plugin command are saved along with the game progress.
 * When loading the map, the settings set by the plugin commands are first set.
 * If there are none, the settings from the tags are loaded.
 *
 * You can also switch the invisibility of the cursor by pressing the keyboard, mouse and gamepad keys.
 * To do this, configure the plugin settings. If you want to specify a key by a string name, set 0 to the number value.
 *
 * ABOUT ANIMATED CURSORS
 * To make the cursor animated, set the CursorFramesNumber value (number of frames) via a parameter, command, or plugin tag.
 * The value must be greater than 1.
 * Animation is configured separately for the image when clicked, if there is one.
 * You must place in the "img/system" folder as many cursor images as you specified frames.
 * Specify the image of the first frame as the cursor image (CursorPicture) in the parameter.
 * Let's say this file is called "cursor.png" and you specified 3 frames.
 * Then the names of the other two files should be as follows:
 * "cursor1.png"
 * "cursor2.png"
 * That is, they must end with the frame number, numbering starts from 0.
 * You do not need to specify the number for the first frame file!
 *
 * You can configure the preloading of cursor resources. This will speed up the switching of cursor images,
 * but it may slow down the launch of the game.
 * Images from the basic cursors (default, battle and menu) are always cached.
 * If preloading of maps is disabled, the map event commands are cached when loading this map.
 * If the preloading of general events is disabled, the commands of this event are cached when it is called.
 *
 * If the cursor image is not displayed, try to reduce its size.
 * 
 * You can always write to the author if you need other features or even plugins.
 * Boosty: https://boosty.to/phileas
 * RPG Maker Web: https://forums.rpgmakerweb.com/index.php?members/phileas.176075/
 * RPG Maker Union: https://rpgmakerunion.ru/id/phileas
 * Email: olek.olegovich gmail.com
 * Telegram: olekolegovich
 *
 * [License]
 * This plugin is released under MIT license.
 * http://opensource.org/licenses/mit-license.php
 *
 * This means that you can freely use the plugin in non-commercial and commercial games and even edit it.
 * But be sure to include me in the credits!
 */
 
/*~struct~CursorDataStruct:
 * @param CursorPicture
 * @text Cursor picture
 * @type file
 * @dir img/system/
 *
 * @param CursorPictureOnClick
 * @text Cursor picture on click
 * @type file
 * @dir img/system/
 * @desc Leave this field empty so that the cursor does not change when clicked
 *
 * @param CursorXOffset
 * @text X offset
 * @number
 * @default 0
 * @min 0
 * @desc The horizontal offset of the click point from the upper-left corner in pixels
 *
 * @param CursorYOffset
 * @text Y offset
 * @number
 * @default 0
 * @min 0
 * @desc The vertical offset of the click point from the upper-left corner in pixels
 *
 * @param CursorFramesNumber
 * @text Frames number
 * @type number
 * @default 1
 * @min 1
 * @desc Set to 1 if the cursor is not animated. For more information, see the description of the plugin
 *
 * @param CursorClickFramesNumber
 * @text Frames number (click)
 * @type number
 * @default 1
 * @min 1
 * @desc Set to 1 if the cursor is not animated. For more information, see the description of the plugin
 */
 
/*~struct~CursorEventDataStruct:
 * @param CursorPicture
 * @text Cursor picture
 * @type file
 * @dir img/system/
 *
 * @param CursorPictureOnClick
 * @text Cursor picture on click
 * @type file
 * @dir img/system/
 * @desc Leave this field empty so that the cursor does not change when clicked
 *
 * @param CursorXOffset
 * @text X offset
 * @number
 * @default 0
 * @min 0
 * @desc The horizontal offset of the click point from the upper-left corner in pixels
 *
 * @param CursorYOffset
 * @text Y offset
 * @number
 * @default 0
 * @min 0
 * @desc The vertical offset of the click point from the upper-left corner in pixels
 *
 * @param CursorFramesNumber
 * @text Frames number
 * @type number
 * @default 1
 * @min 1
 * @desc Set to 1 if the cursor is not animated. For more information, see the description of the plugin
 *
 * @param CursorClickFramesNumber
 * @text Frames number (click)
 * @type number
 * @default 1
 * @min 1
 * @desc Set to 1 if the cursor is not animated. For more information, see the description of the plugin
 *
 * @param CursorStartOnClick
 * @text Start on click
 * @type boolean
 * @default false
 *
 * @param CursorStartOnHover
 * @text Start on hover
 * @type boolean
 * @default false
 */
 
/*:ru
 * @target MZ
 * @plugindesc Расширенная конфигурация курсора
 * @author Phileas
 *
 * @param basicCursors
 * @text Основные курсоры
 *
 * @param defaultCursor
 * @parent basicCursors
 * @text Курсор по умолчанию
 * @type struct<CursorDataStruct>
 * @desc Устанавливается при запуске игры
 *
 * @param battleCursor
 * @parent basicCursors
 * @text Курсор боя
 * @type struct<CursorDataStruct>
 * @desc Используется в бою. Если не установлен, будет использоваться "Курсор по умолчанию"
 *
 * @param menuCursor
 * @parent basicCursors
 * @text Курсор меню
 * @type struct<CursorDataStruct>
 * @desc Используется в меню. Если не установлен, будет использоваться "Курсор по умолчанию"
 *
 * @param cursorDisplay
 * @text Отображение курсора
 *
 * @param hideAtStartup
 * @parent cursorDisplay
 * @text Скрывать при запуске?
 * @type boolean
 * @default false
 * @desc Если true, курсор будет невидимым при запуске игры
 *
 * @param keyboardHideKey
 * @parent cursorDisplay
 * @text Клавиша клавиатуры для скрытия
 * @desc Невидимость курсора будет переключаться при нажатии на заданную клавишу
 *
 * @param keyboardHideKeyNumber
 * @parent cursorDisplay
 * @text Клавиша клавиатуры для скрытия (номер)
 * @type number
 * @default 0
 * @desc Альтернатива параметру "Клавиша скрытия", если вам удобнее использовать числовой код клавиши
 *
 * @param mouseHideKey
 * @parent cursorDisplay
 * @text Клавиша мыши для скрытия
 * @desc Невидимость курсора будет переключаться при нажатии на заданную клавишу
 *
 * @param mouseHideKeyNumber
 * @parent cursorDisplay
 * @text Клавиша мыши для скрытия (номер)
 * @type number
 * @default 0
 * @desc Альтернатива параметру "Клавиша скрытия", если вам удобнее использовать числовой код клавиши
 *
 * @param gamepadHideKey
 * @parent cursorDisplay
 * @text Клавиша геймпада для скрытия
 * @desc Невидимость курсора будет переключаться при нажатии на заданную клавишу
 *
 * @param gamepadHideKeyNumber
 * @parent cursorDisplay
 * @text Клавиша геймпада для скрытия (номер)
 * @type number
 * @default 0
 * @desc Альтернатива параметру "Клавиша скрытия", если вам удобнее использовать числовой код клавиши
 *
 * @param animationPeriod
 * @text Период анимации
 * @type number
 * @default 100
 * @min 0
 * @desc Кол-во миллисекунд, через которые должно происходить обновление кадров анимации курсора.
 *
 * @param mapsPreload
 * @text Предзагрузка карт
 * @type boolean
 * @default false
 * @desc Предзагрузка картинок курсоров с команд на картах. Загружаются внутренние папки любого уровня вложенности.
 *
 * @param commonEventsPreload
 * @text Common events preloading
 * @type boolean
 * @default false
 * @desc Предзагрузка картинок курсоров с команд в событиях. Загружаются внутренние папки любого уровня вложенности.
 *
 * @command setDefaultCursor
 * @text Изменить курсор по умолчанию
 * @arg cursorData
 * @text Настройки
 * @type struct<CursorDataStruct>
 *
 * @command setBattleCursor
 * @text Изменить курсор боя
 * @arg cursorData
 * @text Настройки
 * @type struct<CursorDataStruct>
 *
 * @command setMenuCursor
 * @text Изменить курсор меню
 * @arg cursorData
 * @text Настройки
 * @type struct<CursorDataStruct>
 *
 * @command hide
 * @text Скрыть курсор
 * @desc Делает курсор невидимым
 *
 * @command show
 * @text Показать курсор
 * @desc Делает курсор видимым
 *
 * @command setEventCursorData
 * @text Настроить событие
 * @desc Задание конфигурации курсора для отдельного события
 * @arg eventId
 * @text ID события
 * @type number
 * @desc Введите ID события (это положительное число)
 * @arg cursorEventData
 * @text Настройки
 * @type struct<CursorEventDataStruct>
 *
 * @command setGlobalEventCursorData
 * @text Настроить событие (глобально)
 * @desc Задание конфигурации курсора для отдельного события на любой карте
 * @arg mapId
 * @text ID карты
 * @type number
 * @desc Введите ID карты (это положительное число)
 * @arg eventId
 * @text ID события
 * @type number
 * @desc Введите ID события (это положительное число)
 * @arg cursorEventData
 * @text Настройки
 * @type struct<CursorEventDataStruct>
 *
 * 
 * @help
 * Изменяет картинку курсора на любую из img/system.
 * Поддерживаются анимированные курсоры, подробности ниже.
 * Используйте формат png!
 *
 * Чтобы изменить курсор на стандартный, в параметре/аргументе не выбирайте картинку (вариант "(Нет)").
 * 
 * Команды плагина: 
 * - "Изменить курсор по умолчанию"
 * - "Изменить курсор боя"
 * - "Изменить курсор меню"
 * - "Скрыть курсор"
 * - "Показать курсор"
 * - "Настроить событие" - конфигурирует курсор для отдельного события
 * - "Настроить событие (глобально)" - конфигурирует курсор для события на любой карте
 *
 * Задать конфигурацию курсора для события можно с помощью команды плагина и с помощью тегов в заметках события:
 * <CursorPicture:picture> - эта картинка (picture.png) будет использоваться, когда курсор наведён на событие
 * <CursorPictureOnClick:pictureClick> - картинка, отображаемая при клике (если не задана, курсор не будет меняться при клике)
 * <CursorXOffset:5> - точка клика будет смещена на 5 пикселей по горизонтали от верхнего левого угла
 * <CursorYOffset:5> - точка клика будет смещена на 5 пикселей по вертикали от верхнего левого угла
 * <CursorFramesNumber:3> - курсор будет анимированным с 3 кадрами анимации
 * <CursorClickFramesNumber:3> - курсор при клике будет анимированным с 3 кадрами анимации
 * <CursorStartOnClick> - событие будет запускаться при клике по нему
 * <CursorStartOnHover> - событие будет запускаться при наведении курсора на него
 *
 * Настройки, заданные командой плагина, сохраняются вместе с прогрессом игры. 
 * При загрузке карты сначала устанавливаются настройки, заданные командами плагина.
 * Если их нет - загружаются настройки из тегов.
 *
 * Также вы можете переключать невидимость курсора по нажатию на клавишу клавиатуры, мыши и геймпада.
 * Для этого настройте параметры плагина. Если вы хотите указать клавишу по строковому имени, установите 0 в значение номера.
 *
 * ПРО АНИМИРОВАННЫЕ КУРСОРЫ
 * Чтобы курсор был анимированным, задайте значение CursorFramesNumber (кол-во кадров) через параметр, команду или тег плагина.
 * Значение должно быть больше 1.
 * Отдельно настраивается анимация для изображения при клике, если оно есть.
 * Вы должны расположить в папке "img/system" столько же картинок курсора, сколько указали кадров.
 * В качестве картинки курсора (CursorPicture) в параметре укажите изображение первого кадра.
 * Допустим, этот файл называется "cursor.png" и вы указали 3 кадра.
 * Тогда названия двух остальных файлов должны быть такие:
 * "cursor1.png"
 * "cursor2.png"
 * То есть они должны заканчиваться номером кадра, нумерация начинается с 0.
 * Для файла первого кадра номер указывать не нужно!
 *
 * Вы можете настроить предзагрузку ресурсов курсоров. Это ускорит переключение картинок курсоров,
 * но может замедлить запуск игры.
 * Картинки из базовых курсоров (по умолчанию, бой и меню) кэшируются всегда.
 * Если выключена предзагрузка карт, то команды событий карты кэшируются при загрузке этой карты.
 * Если выключена предзагрузка общих событий, то команды этого события кэшируются при его вызове.
 *
 * Если картинка курсора не отображается, попробуйте уменьшить её размер.
 *
 * Вы всегда можете написать автору, если вам нужны другие функции или даже плагины.
 * Boosty: https://boosty.to/phileas
 * RPG Maker Web: https://forums.rpgmakerweb.com/index.php?members/phileas.176075/
 * RPG Maker Union: https://rpgmakerunion.ru/id/phileas
 * Email: olek.olegovich gmail.com
 * Telegram: olekolegovich
 *
 * [License]
 * Этот плагин распространяется по лицензии MIT.
 * http://opensource.org/licenses/mit-license.php
 *
 * Это означает, что вы можете свободно использовать плагин в некоммерческих и коммерческих играх и даже редактировать его.
 * Но обязательно укажите меня в титрах!
 */
 
/*~struct~CursorDataStruct:ru
 * @param CursorPicture
 * @text Картинка курсора
 * @type file
 * @dir img/system/
 *
 * @param CursorPictureOnClick
 * @text Картинка курсора при клике
 * @type file
 * @dir img/system/
 * @desc Оставьте это поле пустым, чтобы курсор не менялся при клике
 *
 * @param CursorXOffset
 * @text Смещение по X
 * @number
 * @default 0
 * @min 0
 * @desc Отступ точки клика по горизонтали от верхнего левого угла в пикселях
 *
 * @param CursorYOffset
 * @text Смещение по Y
 * @number
 * @default 0
 * @min 0
 * @desc Отступ точки клика по вертикали от верхнего левого угла в пикселях
 *
 * @param CursorFramesNumber
 * @text Кол-во кадров
 * @type number
 * @default 1
 * @min 1
 * @desc Установите 1, если курсор не анимированный. Подробнее в описании плагина
 *
 * @param CursorClickFramesNumber
 * @text Кол-во кадров (клик)
 * @type number
 * @default 1
 * @min 1
 * @desc Установите 1, если курсор не анимированный. Подробнее в описании плагина
 */
 
/*~struct~CursorEventDataStruct:ru
 * @param CursorPicture
 * @text Картинка курсора
 * @type file
 * @dir img/system/
 *
 * @param CursorPictureOnClick
 * @text Картинка курсора при клике
 * @type file
 * @dir img/system/
 * @desc Оставьте это поле пустым, чтобы курсор не менялся при клике
 *
 * @param CursorXOffset
 * @text Смещение по X
 * @number
 * @default 0
 * @min 0
 * @desc Отступ точки клика по горизонтали от верхнего левого угла в пикселях
 *
 * @param CursorYOffset
 * @text Смещение по Y
 * @number
 * @default 0
 * @min 0
 * @desc Отступ точки клика по вертикали от верхнего левого угла в пикселях
 *
 * @param CursorFramesNumber
 * @text Кол-во кадров
 * @type number
 * @default 1
 * @min 1
 * @desc Установите 1, если курсор не анимированный. Подробнее в описании плагина
 *
 * @param CursorClickFramesNumber
 * @text Кол-во кадров (клик)
 * @type number
 * @default 1
 * @min 1
 * @desc Установите 1, если курсор не анимированный. Подробнее в описании плагина
 *
 * @param CursorStartOnClick
 * @text Включать при клике
 * @type boolean
 * @default false
 *
 * @param CursorStartOnHover
 * @text Включать при наведении
 * @type boolean
 * @default false
 */
(function() {
//--------MY CODE:
//-----------------------------------------------------------------------------
// Utils classes
    function getMeta(data) {
        const regExp = /<([^<>:]+)(:?)([^>]*)>/g;
        let meta = {};
        for (;;) {
            const match = regExp.exec(data);
            if (match) {
                if (match[2] === ":") {
                    meta[match[1]] = match[3];
                } else {
                    meta[match[1]] = true;
                }
            } else {
                break;
            }
        }
        
        return meta;
    };
    function PhileasCursorData() {
        this.initialize(...arguments);
    }
    
    PhileasCursorData.prototype.initialize = function() {
        this.CursorPicture = "";
        this.CursorPictureOnClick = "";
        this.CursorFramesNumber = 1;
        this.CursorClickFramesNumber = 1;
        this.CursorXOffset = 1;
        this.CursorYOffset = 1;
    };
    
    PhileasCursorData.prototype.constructor = PhileasCursorData;
    
    PhileasCursorData.prototype.setFromParams = function(params) {
        if (params == "") {
            return;
        }
        
        params = JSON.parse(params);
        this.CursorPicture = params["CursorPicture"] || "";
        this.CursorPictureOnClick = params["CursorPictureOnClick"] || "";
        this.CursorXOffset = Number(params["CursorXOffset"]);
        this.CursorYOffset = Number(params["CursorYOffset"]);
        this.CursorFramesNumber = Number(params["CursorFramesNumber"]);
        this.CursorClickFramesNumber = Number(params["CursorClickFramesNumber"]);
    }
    
    PhileasCursorData.prototype.equals = function(cursorData) {
        if (cursorData instanceof PhileasEventCursorData) {
            return false;
        }
        
        return this.CursorPicture == cursorData.CursorPicture
            && this.CursorPictureOnClick == cursorData.CursorPictureOnClick
            && this.CursorXOffset == cursorData.CursorXOffset
            && this.CursorYOffset == cursorData.CursorYOffset
            && this.CursorFramesNumber == cursorData.CursorFramesNumber
            && this.CursorClickFramesNumber == cursorData.CursorClickFramesNumber;
    }
    function PhileasEventCursorData() {
        this.initialize(...arguments);
    }
    
    PhileasEventCursorData.prototype.initialize = function() {
        PhileasCursorData.prototype.initialize.call(this);
        this.CursorStartOnClick = false;
        this.CursorStartOnHover = false;
    };
    PhileasEventCursorData.prototype = Object.create(PhileasCursorData.prototype);
    PhileasEventCursorData.prototype.constructor = PhileasEventCursorData;
    
    PhileasEventCursorData.prototype.setFromNote = function(note) {
        const meta = getMeta(note);
        if (meta == undefined) {
            return;
        }
        for (let i in phileasCursorTags) {
            let tag = phileasCursorTags[i];
            if (meta[tag] != undefined) {
                this[tag] = meta[tag];
            }
        }
        
        this.CursorXOffset = Number(this.CursorXOffset);
        this.CursorYOffset = Number(this.CursorYOffset);
        this.CursorFramesNumber = Number(this.CursorFramesNumber);
        this.CursorClickFramesNumber = Number(this.CursorClickFramesNumber);
        this.CursorStartOnClick = this.CursorStartOnClick != undefined;
        this.CursorStartOnHover = this.CursorStartOnHover != undefined;
    };
    
    PhileasEventCursorData.prototype.setFromParams = function(params) {
        if (params == "") {
            return;
        }
        
        params = JSON.parse(params);
        this.CursorPicture = params["CursorPicture"] || "";
        this.CursorPictureOnClick = params["CursorPictureOnClick"] || "";
        this.CursorXOffset = Number(params["CursorXOffset"]);
        this.CursorYOffset = Number(params["CursorYOffset"]);
        this.CursorFramesNumber = Number(params["CursorFramesNumber"]);
        this.CursorClickFramesNumber = Number(params["CursorClickFramesNumber"]);
        this.CursorStartOnClick = params["CursorStartOnClick"] == "true";
        this.CursorStartOnHover = params["CursorStartOnHover"] == "true";
    }
    
    PhileasEventCursorData.prototype.equals = function(cursorData) {
        if (!(cursorData instanceof PhileasEventCursorData)) {
            return false;
        }
        
        return this.CursorPicture == cursorData.CursorPicture
            && this.CursorPictureOnClick == cursorData.CursorPictureOnClick
            && this.CursorXOffset == cursorData.CursorXOffset
            && this.CursorYOffset == cursorData.CursorYOffset
            && this.CursorFramesNumber == cursorData.CursorFramesNumber
            && this.CursorClickFramesNumber == cursorData.CursorClickFramesNumber
            && this.CursorStartOnClick == cursorData.CursorStartOnClick
            && this.CursorStartOnHover == cursorData.CursorStartOnHover;
    }
    
//-----------------------------------------------------------------------------
// Data
    const base_url = "./img/system/";
    const x_offset = 0;
    const y_offset = 0;
    const fallbackStyle = "pointer";
    const phileasMouseKeyMap = {
        "left": 0,
        "middle": 1,
        "right": 2
    }
    const phileasCursorTags = ["CursorPicture", "CursorPictureOnClick", "CursorXOffset", "CursorYOffset", "CursorFramesNumber", "CursorClickFramesNumber", "CursorStartOnClick", "CursorStartOnHover"];
    
    var parameters = PluginManager.parameters("Phileas_Cursor");
    var hideAtStartup = parameters["hideAtStartup"] == "true";
    var keyboardHideKey = parameters["keyboardHideKey"] || "";
    var keyboardHideKeyNumber = Number(parameters["keyboardHideKeyNumber"] || 0);
    var mouseHideKey = parameters["mouseHideKey"] || "";
    var mouseHideKeyNumber = Number(parameters["mouseHideKeyNumber"] || 0);
    var gamepadHideKey = parameters["gamepadHideKey"] || "";
    var gamepadHideKeyNumber = Number(parameters["gamepadHideKeyNumber"] || 0);
    var animationPeriod = Number(parameters["animationPeriod"] || 100);
    var mapsPreload = parameters["mapsPreload"] == "true";
    var commonEventsPreload = parameters["commonEventsPreload"] == "true";
    
    var defaultCursor = new PhileasCursorData();
    defaultCursor.setFromParams(parameters["defaultCursor"]);
    var battleCursor = new PhileasCursorData();
    battleCursor.setFromParams(parameters["battleCursor"]);
    var menuCursor = new PhileasCursorData();
    menuCursor.setFromParams(parameters["menuCursor"]);
    var currentHidden = false;
    var currentClick = false;
    var lastFrameIncrementTime = 0; // milliseconds
    var currentFrame = 0;
    var currentClickFrame = 0;
    var currentCursor = new PhileasCursorData();
    var currentCursorSet = {
        "file": "",
        "x": -1,
        "y": -1
    }
    setCurrentCursor(defaultCursor);
    
    PluginManager.registerCommand("Phileas_Cursor", "setDefaultCursor", setDefaultCursor);
    PluginManager.registerCommand("Phileas_Cursor", "setBattleCursor", setBattleCursor);
    PluginManager.registerCommand("Phileas_Cursor", "setMenuCursor", setMenuCursor);
    PluginManager.registerCommand("Phileas_Cursor", "hide", hide);
    PluginManager.registerCommand("Phileas_Cursor", "show", show);
    PluginManager.registerCommand("Phileas_Cursor", "setEventCursorData", setEventCursorData);
    PluginManager.registerCommand("Phileas_Cursor", "setGlobalEventCursorData", setGlobalEventCursorData);
    
    // key - mapId, value = dictionary<eventId, PhileasEventCursorData>
    var phileasGlobalEventCursorData = {};
   
//-----------------------------------------------------------------------------
// Preloading
    var phileasCursorsCash = new Set();
    function preloadCursorImage(file) {
        if (phileasCursorsCash.has(file)) {
            return;
        }
        
        //ImageManager.loadSystem(filename);
        const img = new Image();
        img.src = `${base_url}${file}.png`;
        img.onload = () => phileasCursorsCash.add(file);
        img.onerror = () => console.log(`Failed to load cursor image ${file}.png`);
    }
    
    function preloadCursorData(cursorData) {
        if (cursorData.CursorPicture != "") {
            preloadCursorImage(cursorData.CursorPicture);
            for (let i = 1; i < cursorData.CursorFramesNumber; ++i) {
                preloadCursorImage(cursorData.CursorPicture + String(i));
            }
        }
        
        if (cursorData.CursorPictureOnClick != "") {
            preloadCursorImage(cursorData.CursorPictureOnClick);
            for (let i = 1; i < cursorData.CursorClickFramesNumber; ++i) {
                preloadCursorImage(cursorData.CursorPicture + String(i));
            }
        }
    }
    
    function preloadCursorDataFromCommandList(list) {
        let cursorCommandData = new PhileasEventCursorData();
        for (let i = 0; i < list.length; ++i) {
            if (list[i].code != 357 || list[i].parameters[0] != "Phileas_Cursor") {
                continue;
            }
            
            switch (list[i].parameters[1]) {
                case "setDefaultCursor":
                case "setBattleCursor":
                case "setMenuCursor":
                    cursorCommandData.setFromParams(list[i].parameters[3].cursorData);
                    break;
                case "setEventCursorData":
                case "setGlobalEventCursorData":
                    cursorCommandData.setFromParams(list[i].parameters[3].cursorEventData);
                    break;
                default:
                    continue;
            }
            
            preloadCursorData(cursorCommandData);
        }
    }
    
    function preloadCursorDataFromPages(pages) {
        for (let i = 0; i < pages.length; ++i) {
            preloadCursorDataFromCommandList(pages[i].list);
        }
    }
    
    function preloadCursorDataFromEvent(eventData) {
        let cursorNoteData = new PhileasEventCursorData();
        cursorNoteData.setFromNote(eventData.note);
        preloadCursorData(cursorNoteData);
        preloadCursorDataFromPages(eventData.pages);
    }
    
    function preloadCursorDataFromMaps() {
        let preloadCounter = 1;
        
        const Origin_onDataLoad = DataManager.onLoad;
        DataManager.onLoad = function(object) {
            Origin_onDataLoad.call(this, object);
            if (object.events) {
                for (let i = 1; i < object.events.length; ++i) {
                    if (object.events[i]) {
                        preloadCursorDataFromEvent(object.events[i]);
                    }
                }
            }
            
            ++preloadCounter;
            if (preloadCounter == $dataMapInfos.length) {
                DataManager.onLoad = Origin_onDataLoad;;
            }
        };
        
        for (let i = 1; i < $dataMapInfos.length; ++i) {
            if ($dataMapInfos[i]) {
                DataManager.loadMapData($dataMapInfos[i].id);
            }
        }
    }
    
    function preloadCursorDataFromCommonEvents() {
        for (let i = 1; i < $dataCommonEvents.length; ++i) {
            preloadCursorDataFromCommandList($dataCommonEvents[i].list);
        }
    }
    
    function preloadCursorImages() {
        preloadCursorData(defaultCursor);
        preloadCursorData(battleCursor);
        preloadCursorData(menuCursor);
        
        if (commonEventsPreload) {
            preloadCursorDataFromCommonEvents();
        }
        
        if (mapsPreload) {
            preloadCursorDataFromMaps();
        }
    }
    
//-----------------------------------------------------------------------------
// Main
 
    function setPhileasCursorConfiguration(data) {
        let file = "";
        
        if (currentClick && data.CursorPictureOnClick != "") {
            file = data.CursorPictureOnClick;
            if (currentClickFrame > 0) {
                file += String(currentClickFrame);
            }
        } else {
            file = data.CursorPicture;
            if (currentFrame > 0) {
                file += String(currentFrame);
            }
        }
        
        if (currentCursorSet.file == file
            && currentCursorSet.x == data.CursorXOffset
            && currentCursorSet.y == data.CursorYOffset) {
            return;
        }
        
        currentCursorSet.file = file;
        currentCursorSet.x = data.CursorXOffset;
        currentCursorSet.y = data.CursorYOffset;
        document.body.style.cursor = file == "" 
            ? "default"
            : `url("${base_url}${file}.png") ${data.CursorXOffset} ${data.CursorYOffset}, ${fallbackStyle}`;
    }
    
    function updatePhileasCursor() {
        const now = Date.now();
        const passedTime = now - lastFrameIncrementTime;
        if (currentClick) {
            if (currentCursor.CursorClickFramesNumber > 1 && passedTime > animationPeriod) {
                currentClickFrame = (currentClickFrame + 1) % currentCursor.CursorClickFramesNumber;
                lastFrameIncrementTime = now;
            }
        } else if (currentCursor.CursorFramesNumber > 1 && passedTime > animationPeriod) {
            currentFrame = (currentFrame + 1) % currentCursor.CursorFramesNumber;
            lastFrameIncrementTime = now;
        }
        
        setPhileasCursorConfiguration(currentCursor);
        requestAnimationFrame(updatePhileasCursor);
    }
    requestAnimationFrame(updatePhileasCursor);
    
    function setCurrentCursor(cursorData) {
        if (currentCursor.equals(cursorData)) {
            return;
        }
        
        currentCursor = cursorData;
        currentFrame = currentClickFrame = 0;
    }
    function setDefaultCursor(params) {
        defaultCursor.setFromParams(params);
    }
    
    function setBattleCursor(params) {
        battleCursor.setFromParams(params);
    }
    
    function setMenuCursor(params) {
        menuCursor.setFromParams(params);
    }
    
    function changeCursorToBasic() {
        let scene = SceneManager._scene;
        
        if (battleCursor.CursorPicture != "" && scene instanceof Scene_Battle) {
            setCurrentCursor(battleCursor);
            return;
        }
        
        if (menuCursor.CursorPicture != "" && scene instanceof Scene_MenuBase) {
            setCurrentCursor(menuCursor);
            return;
        }
        
        setCurrentCursor(defaultCursor);
    }
    
    function hide() {
        currentHidden = true;
        document.body.style.cursor = "none";
    }
    
    function show() {
        currentHidden = false;
        changeCursorToBasic();
    }
    
    function refreshEventCursor(eventId, cursorData) {
        let scene = SceneManager._scene;
          
        if (!(scene instanceof Scene_Map) || eventId < 1 || eventId > $gameMap.events().length) {
            return;
        }
        
        const event = $gameMap.event(eventId);
        event.phileasCursorData = cursorData;
    }
    
    function updateEventCursorData(mapId, params) {
        const eventId = Number(params["eventId"]);
        
        let cd = new PhileasEventCursorData();
        cd.setFromParams(params["cursorEventData"]);
        
        if (phileasGlobalEventCursorData[mapId] == undefined) {
            phileasGlobalEventCursorData[mapId] = {};
        }
        
        phileasGlobalEventCursorData[mapId][eventId] = cd;
        
        if ($gameMap.mapId() == mapId) {
            refreshEventCursor(eventId, cd);
        }
    }
    
    function setEventCursorData(params) {
        updateEventCursorData($gameMap.mapId(), params);
    }
    
    function setGlobalEventCursorData(params) {
        const mapId = Number(params["mapId"]);
        updateEventCursorData(mapId, params);
    }
    
    function switchHide() {
        if (currentHidden) {
            show();
        }
        else {
            hide();
        }
    }
    
    function getKeyByValue(object, value) {
        return Object.keys(object).find(key => object[key] === value);
    }
    
    function phileasCursorDownHandler(event) {
        if (event.keyCode == keyboardHideKeyNumber) {
            switchHide();
        }
    }
    
    function phileasCursorMouseDownHandler(event) {
        if (event.button == mouseHideKeyNumber) {
            switchHide();
        }
    }
    
    if (hideAtStartup) {
        hide();
    }
    if (keyboardHideKeyNumber == 0) {
        keyboardHideKeyNumber = getKeyByValue(Input.keyMapper, keyboardHideKey);
    }
    if (mouseHideKeyNumber == 0) {
        mouseHideKeyNumber = phileasMouseKeyMap[mouseHideKey];
    }
    if (gamepadHideKeyNumber == 0) {
        gamepadHideKeyNumber = getKeyByValue(Input.gamepadMapper, gamepadHideKey);
    }
    if (keyboardHideKeyNumber != 0) {
        document.addEventListener("keydown", phileasCursorDownHandler);
    }
    if (mouseHideKeyNumber != 0) {
        document.addEventListener("mousedown", phileasCursorMouseDownHandler);
    }
    
    function phileasCursorClickDownHandler(event) {
        if (event.button == 0) {
            currentClick = true;
        }
    }
    
    function phileasCursorClickUpHandler(event) {
        if (event.button == 0) {
            currentClick = false;
        }
    }
    
    document.addEventListener("mousedown", phileasCursorClickDownHandler);
    document.addEventListener("mouseup", phileasCursorClickUpHandler);
    
    
    Scene_Map.prototype.setupPhileasEventCursors = function() {
        if (!(SceneManager._scene instanceof Scene_Map)) {
            return;
        }
        let events = SceneManager._scene._spriteset._characterSprites;
        SceneManager._scene.phileasLabelWindows = {};
        for (var i = 0; i < events.length; ++i) {
            if (!(events[i]._character instanceof Game_Event)) {
                continue;
            }
            const ch = events[i]._character;
            if (!mapsPreload) {
                preloadCursorDataFromPages(ch.event().pages);
            }
            
            if (phileasGlobalEventCursorData[$gameMap.mapId()] != undefined) {
                const cursorData = phileasGlobalEventCursorData[$gameMap.mapId()][ch.eventId()];
                if (cursorData != undefined) {
                    ch.phileasCursorData = cursorData;
                    continue;
                }
            }
            events[i]._character.phileasCursorData = new PhileasEventCursorData();
            events[i]._character.phileasCursorData.setFromNote(ch.event().note);
        }
    };
    
    Scene_Map.prototype.phileasCheckEventCursors = function(x, y) {
        const mapX = $gameMap.canvasToMapX(x);
        const mapY = $gameMap.canvasToMapY(y);
        const events = $gameMap.eventsXy(mapX, mapY);
        if (events.length === 0) {
            changeCursorToBasic();
        }
        
        for (let i = 0; i < events.length; ++i) {
            const cursorData = events[i].phileasCursorData;
            if (cursorData.CursorStartOnHover) {
                events[i].start();
            }
            
            if (cursorData.CursorPicture) {
                setCurrentCursor(cursorData);
            }
        }
    };
    
    TouchInput.phileasCheckClickEventCursors = function(x, y) {
        const scene = SceneManager._scene;
        if (!(scene instanceof Scene_Map) || !scene.isActive() || $gameMessage.isBusy()) {
            return false;
        }
        
        const mapX = $gameMap.canvasToMapX(x);
        const mapY = $gameMap.canvasToMapY(y);
        const events = $gameMap.eventsXy(mapX, mapY);
        
        for (let i = 0; i < events.length; ++i) {
            if (events[i]._erased) {
                return false;
            }
            
            const cursorData = events[i].phileasCursorData;
            if (cursorData.CursorStartOnClick) {
                events[i].start();
                return true;
            }
        }
        
        return false;
    };
//--------CHANGED CORE:
    
    const Origin_loaded = Scene_Boot.prototype.onDatabaseLoaded;
    Scene_Boot.prototype.onDatabaseLoaded = function() {
        Origin_loaded.call(this);
        preloadCursorImages();
    };
    const Origin_setupNewGame = DataManager.setupNewGame;
    DataManager.setupNewGame = function() {
        Origin_setupNewGame.call(this);
        changeCursorToBasic();
    };
    
    const Origin_makeSaveContents = DataManager.makeSaveContents;
    DataManager.makeSaveContents = function() {
        let contents = Origin_makeSaveContents.call(this);
        contents.phileasDefaultCursor = defaultCursor;
        contents.phileasBattleCursor = battleCursor;
        contents.phileasMenuCursor = menuCursor;
        contents.phileasCursorHidden = currentHidden;
        contents.phileasGlobalEventCursorData = phileasGlobalEventCursorData;
        return contents;
    };
    
    const Origin_extractSaveContents = DataManager.extractSaveContents;
    DataManager.extractSaveContents = function(contents) {
        Origin_extractSaveContents.call(this, contents);
        defaultCursor = contents.phileasDefaultCursor || new PhileasCursorData();
        battleCursor = contents.phileasBattleCursor || new PhileasCursorData();
        menuCursor = contents.phileasMenuCursor || new PhileasCursorData();
        phileasGlobalEventCursorData = contents.phileasGlobalEventCursorData || {};
        changeCursorToBasic();
        if (contents.phileasCursorHidden || false) {
            hide();
        }
        else {
            show();
        }
    };
    
    if (gamepadHideKeyNumber != 0) {
        Origin_updateGamepadState = Input._updateGamepadState;
        Input._updateGamepadState = function(gamepad) {
            Origin_updateGamepadState.call(this, gamepad);
            const lastState = this._gamepadStates[gamepad.index] || [];
            let state = this._gamepadStates[gamepad.index];
            for (let i = 0; i < state.length; ++i) {
                if (state[i] == true && lastState[i] != true && i == gamepadHideKeyNumber) {
                    switchHide();
                }
            }
        };
    }
    const Origin_onMapLoaded = Scene_Map.prototype.onMapLoaded;
    Scene_Map.prototype.onMapLoaded = function() {
        Origin_onMapLoaded.call(this);
        this.setupPhileasEventCursors();
    };
    
    const Origin_onMouseMove = TouchInput._onMouseMove;
    TouchInput._onMouseMove = function(event) {
        Origin_onMouseMove.call(this, event);
        const x = Graphics.pageToCanvasX(event.pageX);
        const y = Graphics.pageToCanvasY(event.pageY);
        const scene = SceneManager._scene;
        if (!currentHidden
            && Graphics.isInsideCanvas(x, y) 
            && scene instanceof Scene_Map 
            && scene.isActive() 
            && !$gameMessage.isBusy()) {
            scene.phileasCheckEventCursors(this._x, this._y);
        }
    };
    
    const Origin_onTrigger = TouchInput._onTrigger;
    TouchInput._onTrigger = function(x, y) {
        if (this.phileasCheckClickEventCursors(x, y)) {
            $gameTemp.clearDestination();
            return;
        }
        
        Origin_onTrigger.call(this, x, y);
    };
    
    if (!commonEventsPreload) {
        const Origin_CommonEventInitialize = Game_CommonEvent.prototype.initialize;
        Game_CommonEvent.prototype.initialize = function(commonEventId) {
            preloadCursorDataFromCommandList($dataCommonEvents[commonEventId].list);
            Origin_CommonEventInitialize.call(commonEventId);
        };
    }
}());
 
Социальные закладки